/**
 * Copyright 2000 self.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 *
 * @author Ashin Wimalajeewa
 */
package self.amigo.elem;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import self.awt.ILineRenderInfo;
import self.awt.PainterManager;
import self.awt.painter.LinePainter;
import self.gee.IDiagramLayer;
import self.gee.IGraphicElement;
import self.gee.IPointSeriesElement;
import self.lang.BooleanUtils;
import self.lang.StringUtils;
import self.math.EllipseEquation;
import self.math.LinearEquation;
import self.xml.DomUtils;

public class PolyLineView extends AGraphicElement implements IPointSeriesElement {
	
	/**
	 * If the internal state of this class ever changes in such a way that it can't be defaulted,
	 * then the {@link #serialVersionUID} should be incremented to ensure serialized instances cleanly fail.  
	 */
	private static final long serialVersionUID = 1;
	
  final static public  String DEFAULT_STYLE = "*0:0";
  final static public  float DEFAULT_WIDTH = 1.0f;

  final static public  String POLYGON_PROP = "polygon";
  final static public  String POLYGON_FRAME_PROP = "poly-frame";
  final static public  String POLYGON_FILL1_PROP = "poly-fill1";
  final static public  String POLYGON_FILL2_PROP = "poly-fill2";
  final static public  String POLYGON_FILL_STYLE_PROP = "poly-fill-style";
  final static public  String HEADER_PROP = "header";
  final static public  String NAME_PROP = "name";
  final static public  String DASHED_PROP = "dashed";
  final static public  String WIDTH_PROP = "width";
  final static public  String SIDEA_PROP = "SideA";
  final static public  String SIDEB_PROP = "SideB";
  final static public  String HEAD_DIAMETER_PROP = "headDiameter";

  final static public String[] POINT_PROPS = { "Points", "Point" };

  protected ArrayList     points = new ArrayList();
  transient private EllipseEquation hitCircle = new EllipseEquation();  // consider creating a line hit helper
  transient private LinearEquation  hitLine = new LinearEquation();
  private Point           inserted;
  private boolean         isPolygon = false;
  private Polygon         lineAsPolygon;

  protected LineSideFeature a = new LineSideFeature();
  protected LineSideFeature b = new LineSideFeature();
  protected String          name, header;
  protected int             headDiameter = 12;
  protected float           lineWidth = DEFAULT_WIDTH;
  protected boolean         isDashed = false;
  protected boolean         showPolyFrame = true;
  protected LinePainter     linePainter;

  private   ColorFeature    lineColorData = new ColorFeature( FRAMECOLOR_PROP );
  private   Color           polyonGrad1Color = Color.white, polyonGrad2Color;
  private   String          polygonEffectStyle = DEFAULT_STYLE;
  private   int             polygonEffectDirection, polygonEffectLen;
  private   boolean         polygonEffectCyclic;

  transient protected PaintContext  context = new PaintContext();

  public PolyLineView() {
    resetPolygonFrame();
    resetPolygonEffect();
  }

  // IPointSeriesElement

  public void getFramePoints( java.util.List store ) {
    getHandlePoints( store );
  }

  public void setFramePoints( java.util.List data ) {
    points.clear();
    points.addAll( data );
    resetPolygonFrame();
  }

  public void       setLayer( IDiagramLayer layerToDrawOn, boolean add ) {
    super.setLayer( layerToDrawOn, add );
    linePainter = (LinePainter) PainterManager.getPainterForOwner( layer, LinePainter.class );
  }

  public void       paint( Graphics surface ) {
    if ( isPolygon )
      paintPolygon( surface );
    else
      linePainter.paint( surface, context, outlineOnly );
  }

  private void paintPolygon( Graphics graphics ) {
    Graphics2D surface = (Graphics2D) graphics;
    if (!outlineOnly && (polyonGrad1Color != null) ) {
      if ( polyonGrad2Color != null ) {
        Paint p = surface.getPaint();
        try {
          ArrayList list = PolygonEffectUtils.assembleEffect( lineAsPolygon, polyonGrad1Color, polyonGrad2Color, polygonEffectDirection, polygonEffectLen, polygonEffectCyclic );
          int max = list.size();
          for ( int cntr = 0; cntr < max; cntr++ ) {
            PolygonEffectUtils.GradientPaintShape info = (PolygonEffectUtils.GradientPaintShape) list.get( cntr );
            surface.setPaint( info.gradientPaint );
            surface.fill( info.shape );
          }
        } finally {
          surface.setPaint( p );
        }
      } else {
        surface.setColor( polyonGrad1Color );
        surface.fillPolygon( lineAsPolygon );
      }
    }
    if ( outlineOnly || showPolyFrame ) {
      Stroke curr = ((Graphics2D) surface).getStroke();
      if ( !outlineOnly ) {
        BasicStroke strokeToUse = LinePainter.initBasicStroke( context );
        ((Graphics2D) surface).setStroke( strokeToUse );
      }
      surface.setColor( frameColor );
      surface.drawPolygon( lineAsPolygon );
      if ( !outlineOnly )
        ((Graphics2D) surface).setStroke( curr );
    }
  }

  public void       setLocation( int x, int y ) {
    if ( points.size() > 0 ) {
      Point firstPoint = (Point) points.get( 0 );
      firstPoint.setLocation( x, y );
    } else
      points.add( new Point(x, y) );
    resetPolygonFrame();
    layer.setDirty();
  }

  public void       move( Dimension moveBy, MouseEvent e, int dragState ) {
    if ( dragState == IGraphicElement.DS_AFTER_LAST ) {
      outlineOnly = false;
      layer.setDirty();
      return;
    }
    outlineOnly = true;
    if ( dragState == IGraphicElement.DS_FIRST )
      inserted = null;
    int x = e.getX(), y = e.getY();
    if ( inserted != null )
      inserted.setLocation( x, y );
    else if ( (dragState == IGraphicElement.DS_FIRST) && e.isControlDown() ) {
      inserted = new Point( x, y );
      Point[] ab = new Point[2];
      if ( containsWithPoints(inserted, ab) )
        insertPointBetween( ab[0], ab[1], inserted );
    } else {
      int max = points.size();
      for ( int cntr = 0; cntr < max; cntr++ ) {
        Point pointToMove = (Point) points.get( cntr );
        pointToMove.translate( moveBy.width, moveBy.height );
      }
    }
    resetPolygonFrame();
  }

  public void       resize( int x, int y, Point[] handle, MouseEvent e, int dragState ) {
    if ( dragState == IGraphicElement.DS_AFTER_LAST ) {
      outlineOnly = false;
      layer.setDirty();
      return;
    }
    outlineOnly = true;
    int pos = getPositionOfPoint( handle[0] );
    if ( pos == -1 )
      return;
    handle[0].setLocation( x, y );        // Standard move
    eatHandlePointIfJoinAttempted( x, y, handle[0] );
    if ( isPolygon )
      isPolygon = (getNoOfPoints() > 2);
    resetPolygonFrame();
  }

  public boolean getLineIntersectionToCentre( int xFrom, int yFrom, int[] xyPoint ) {
    int noOfPoints = points.size();
    if ( noOfPoints < 2 )
      return false;
    return fetchIntersection( xFrom, yFrom, xyPoint );
  }

  public void       getHandlePoints( java.util.List store ) {
    int max = points.size();
    for ( int cntr = 0; cntr < max; cntr++ )
      store.add( points.get(cntr) );
  }

  public boolean    contains( Point midpoint ) {
    if ( isPolygon )
      return lineAsPolygon.contains( midpoint );
    return containsWithPoints( midpoint, null );
  }

  public boolean    isWithin( Rectangle within ) {
    if ( isPolygon )
      return lineAsPolygon.intersects( within.getX(), within.getY(), within.getWidth(), within.getHeight() );

    Point pointA = (Point) points.get( 0 );
    int max = points.size();
    for (int cntr = 1; cntr < max; cntr++) {
      final Point pointB = (Point) points.get( cntr );
      if (within.intersectsLine(pointA.x, pointA.y, pointB.x, pointB.y)) {
        return true;
      }
      pointA = pointB;
    }
    return false;
  }

  public Rectangle getBounds() {
    Point firstPoint = (Point) points.get( 0 );
    Rectangle bounds = new Rectangle( firstPoint.x, firstPoint.y, 1, 1 );
    int max = points.size();
    for ( int cntr = 1; cntr < max; cntr++ )
      bounds.add( (Point) points.get(cntr) );
    return bounds;
  }

  public Rectangle  getOverallBounds() {
    return getBounds();
  }

  // IEditableElement support
  public void       getProperties( Map store ) {
    store.put( PolyLineView.POLYGON_PROP, (isPolygon ? Boolean.TRUE: Boolean.FALSE) );
    store.put( PolyLineView.POLYGON_FRAME_PROP, (showPolyFrame ? Boolean.TRUE: Boolean.FALSE) );
    store.put( PolyLineView.POLYGON_FILL_STYLE_PROP, polygonEffectStyle );
    store.put( PolyLineView.POLYGON_FILL1_PROP, polyonGrad1Color );
    store.put( PolyLineView.POLYGON_FILL2_PROP, polyonGrad2Color );

    store.put( PolyLineView.DASHED_PROP, (isDashed ? Boolean.TRUE: Boolean.FALSE) );
    store.put( PolyLineView.HEADER_PROP, header );
    store.put( PolyLineView.WIDTH_PROP, "" + lineWidth );
    store.put( PolyLineView.NAME_PROP, name );
    LineSideFeature clonedA = (LineSideFeature)a.clone(), clonedB = (LineSideFeature)b.clone();
    store.put( PolyLineView.SIDEA_PROP, clonedA );
    store.put( PolyLineView.SIDEB_PROP, clonedB );
    store.put( PolyLineView.HEAD_DIAMETER_PROP, "" + headDiameter );
    lineColorData.getProperties( store );
  }

  public void       setProperties( Map data ) {
    Boolean dash = (Boolean) data.get( PolyLineView.DASHED_PROP );
    isDashed = dash.booleanValue();
    header = (String) data.get( PolyLineView.HEADER_PROP );
    name = (String) data.get( PolyLineView.NAME_PROP );
    LineSideFeature newA = (LineSideFeature) data.get( PolyLineView.SIDEA_PROP );
    a.set( newA );

    LineSideFeature newB = (LineSideFeature) data.get( PolyLineView.SIDEB_PROP );
    b.set( newB );

    lineColorData.setProperties( data );

    String headDiamAsString = (String) data.get( PolyLineView.HEAD_DIAMETER_PROP );
    if ( !StringUtils.isNullOrEmpty(headDiamAsString) ) {
      try {
        headDiameter = Integer.parseInt(headDiamAsString);
      } catch ( Exception err ) {
    	// nothing to do.
      }
    }

    String lineWidthAsString = (String) data.get( PolyLineView.WIDTH_PROP );
    if ( !StringUtils.isNullOrEmpty(lineWidthAsString) ) {
      try {
        lineWidth = Float.parseFloat(lineWidthAsString);
      } catch ( Exception err ) {
    	// nothing to do.
      }
    }

    Boolean poly = (Boolean) data.get( PolyLineView.POLYGON_PROP );
    isPolygon = poly.booleanValue();

    Boolean polyFrame = (Boolean) data.get( PolyLineView.POLYGON_FRAME_PROP );
    showPolyFrame = polyFrame.booleanValue();

    polygonEffectStyle = (String) data.get( PolyLineView.POLYGON_FILL_STYLE_PROP );
    polyonGrad1Color = (Color) data.get( PolyLineView.POLYGON_FILL1_PROP );
    polyonGrad2Color = (Color) data.get( PolyLineView.POLYGON_FILL2_PROP );

    resetPolygonEffect();
    layer.setDirty();
  }

  // IPersistentElement support
  public void readFrom( Node self, HashMap idObjLookUp ) throws DOMException {
    readGeneralLineInfo( self, idObjLookUp );
    readPoints( self );

    if ( isPolygon )
      readPolygonInfo( self );
    else {
      Node sideA = DomUtils.getChildElementNode( self, PolyLineView.SIDEA_PROP );
      readSideA( sideA, idObjLookUp );

      Node sideB = DomUtils.getChildElementNode( self, PolyLineView.SIDEB_PROP );
      readSideB( sideB, idObjLookUp );
      headDiameter = Integer.parseInt( DomUtils.getElementAttribute(self, PolyLineView.HEAD_DIAMETER_PROP, "12") );
    }
  }

  protected void readPolygonInfo( Node self ) throws DOMException {
    String polyFrameAsString = DomUtils.getElementAttribute(self, POLYGON_FRAME_PROP, "true");
    showPolyFrame = BooleanUtils.toValue( polyFrameAsString );
    polygonEffectStyle = DomUtils.getElementAttribute( self, POLYGON_FILL_STYLE_PROP, DEFAULT_STYLE );
    String colorAsString;
    colorAsString = DomUtils.getElementAttribute( self, POLYGON_FILL1_PROP, null );
    if ( colorAsString != null )
      polyonGrad1Color = new Color( Integer.parseInt(colorAsString) );
    colorAsString = DomUtils.getElementAttribute( self, POLYGON_FILL2_PROP, null );
    if ( colorAsString != null )
      polyonGrad2Color = new Color( Integer.parseInt(colorAsString) );
    resetPolygonEffect();
  }

  protected void readPoints( Node selfRef ) throws DOMException {
    ArrayList list = new ArrayList();
    DomUtils.readListOfValues( selfRef, POINT_PROPS, list );
    Point[] source = new Point[1];
    for ( int cntr = 0; cntr < list.size(); cntr++ ) {
      String pointAsString = list.get(cntr).toString();
      source[0] = new Point();
      self.awt.PointUtils.parsePoint( pointAsString, source );
      points.add( source[0] );
    }
    resetPolygonFrame();
  }

  protected void readGeneralLineInfo( Node self, HashMap idObjLookUp ) throws DOMException {
    header = DomUtils.getElementAttribute( self, HEADER_PROP, "" );
    name = DomUtils.getElementAttribute( self, NAME_PROP, "" );
    lineWidth = Float.parseFloat( DomUtils.getElementAttribute(self, PolyLineView.WIDTH_PROP, "1.0") );
    String dashedAsString = DomUtils.getElementAttribute(self, DASHED_PROP, "false");
    isDashed = BooleanUtils.toValue( dashedAsString );
    String polyAsString = DomUtils.getElementAttribute(self, POLYGON_PROP, "false");
    isPolygon = BooleanUtils.toValue( polyAsString );
    lineColorData.readFrom( self, idObjLookUp );
  }

  protected void readSideA( Node side, HashMap idObjLookUp ) throws DOMException {
    a.readFrom( side, idObjLookUp );
  }

  protected void readSideB( Node side, HashMap idObjLookUp ) throws DOMException {
    b.readFrom( side, idObjLookUp );
  }

  public void writeTo( Document doc, Element self, HashMap objIdLookup ) throws DOMException {
    writeGeneralLineInfo( doc, self, objIdLookup );
    writePoints( self );
    if ( isPolygon )
      writePolygonInfo( self );
    else {
      Element sideA = doc.createElement( SIDEA_PROP );
      self.appendChild( sideA );
      writeSideA( doc, sideA, objIdLookup );

      Element sideB = doc.createElement( SIDEB_PROP );
      self.appendChild( sideB );
      writeSideB( doc, sideB, objIdLookup );

      if ( headDiameter != 12 )
        DomUtils.setElementAttribute( self, HEAD_DIAMETER_PROP, "" + headDiameter );
    }
  }

  protected void writePolygonInfo( Element self ) {
    if ( !showPolyFrame )
      DomUtils.setElementAttribute( self, POLYGON_FRAME_PROP, "" + showPolyFrame );
    DomUtils.setElementAttribute( self, POLYGON_PROP, "" + isPolygon );
    if ( !DEFAULT_STYLE.equals(polygonEffectStyle) )
      DomUtils.setElementAttribute( self, POLYGON_FILL_STYLE_PROP, polygonEffectStyle );
    if ( polyonGrad1Color != null )
      DomUtils.setElementAttribute( self, POLYGON_FILL1_PROP, String.valueOf(polyonGrad1Color.getRGB()) );
    if ( polyonGrad2Color != null )
      DomUtils.setElementAttribute( self, POLYGON_FILL2_PROP, String.valueOf(polyonGrad2Color.getRGB()) );
  }

  protected void writePoints( Element selfRef ) {
    ArrayList list = new ArrayList();
    int max = points.size();
    for ( int cntr = 0; cntr < max; cntr++ ) {
      String pointAsString = self.awt.PointUtils.toString( (Point) points.get(cntr) );
      list.add( pointAsString );
    }
    DomUtils.writeListOfValues( selfRef, POINT_PROPS, list );
  }

  protected void writeSideA( Document doc, Element side, HashMap objIdLookup ) {
    a.writeTo( doc, side, objIdLookup );
  }

  protected void writeSideB( Document doc, Element side, HashMap objIdLookup ) {
    b.writeTo( doc, side, objIdLookup );
  }

  protected void writeGeneralLineInfo( Document doc, Element self, HashMap objIdLookup ) throws DOMException {
    if ( !StringUtils.isNullOrEmpty(header) )
      DomUtils.setElementAttribute( self, HEADER_PROP, StringUtils.toEmptyIfNull(header) );
    if ( !StringUtils.isNullOrEmpty(name) )
      DomUtils.setElementAttribute( self, NAME_PROP, StringUtils.toEmptyIfNull(name) );
    if ( isDashed )
      DomUtils.setElementAttribute( self, DASHED_PROP, "" + isDashed );
    if ( lineWidth != DEFAULT_WIDTH )
      DomUtils.setElementAttribute( self, WIDTH_PROP, "" + lineWidth );
    lineColorData.writeTo( doc, self, objIdLookup );
  }

  public void setLastLocation( int x, int y ) {
    Point last;
    int max = points.size();
    if ( max <= 1 ) {
      last = new Point( x, y );
      points.add( last );
    } else {
      last = (Point) points.get( max -1 );
      last.setLocation( x, y );
    }
    resetPolygonFrame();
    layer.setDirty();
  }

  public void insertPointBetween(Point pointA, Point pointB, Point insert) {
    final int posA = getPositionOfPoint(pointA);
    final int posB = getPositionOfPoint(pointB);
    final int max = Math.max(posA, posB);
    points.add(max, insert);
  }

  public void addPoint( Point p ) {
    points.add( p );
  }

  public int getNoOfPoints() {
    return points.size();
  }

  public String toString() {
    int max = points.size();
    StringBuffer pts = new StringBuffer( 80 );
    for ( int cntr = 0; cntr < max; cntr++ ) {
      if ( cntr > 0 )
        pts.append( ", " );
      String pointAsString = points.get(cntr).toString();
      int pos = pointAsString.indexOf( "[" );
      pts.append( pointAsString.substring(pos) );
    }
    return "[" + this.getClass().getName() + "@" + hashCode() +"]" + pts.toString();
  }

  protected int getMinEatHandles() {
    return 2;
  }

  protected String decorateHeader( String hdr ) {
    return hdr;
  }

  private void resetPolygonFrame() {
    int max = getNoOfPoints();
    int[] xpoints = new int[max], ypoints = new int[max];
    for ( int cntr = 0; cntr < max; cntr++ ) {
      Point p = (Point) points.get( cntr );
      xpoints[ cntr ] = p.x;
      ypoints[ cntr ] = p.y;
    }
    lineAsPolygon = new Polygon( xpoints, ypoints, max );
  }

  private void resetPolygonEffect() {
    if ( polygonEffectStyle == null )
      polygonEffectStyle = DEFAULT_STYLE;
    String spec = polygonEffectStyle;
    polygonEffectCyclic = spec.startsWith("*");
    if ( polygonEffectCyclic )
      spec = spec.substring( 1 );

    int pos = spec.indexOf( ':' );
    if ( pos == -1 ) {
      polygonEffectDirection = 0;
      polygonEffectLen = 0;
      polygonEffectStyle = DEFAULT_STYLE;
    } else {
      try {
        String tmp = spec.substring(0, pos);
        polygonEffectDirection = Integer.parseInt( tmp );
      } catch ( Exception err ) {
        polygonEffectDirection = 0;
      }
      try {
        String tmp = spec.substring(pos+1);
        polygonEffectLen = Integer.parseInt( tmp );
      } catch ( Exception err ) {
        polygonEffectLen = 0;
      }
    }
  }

  private int getPositionOfPoint( Point find ) {
    int max = points.size();
    for ( int cntr = 0; cntr < max; cntr++ ) {
      if ( points.get(cntr) == find )
        return cntr;
    }
    return -1;
  }

  private boolean containsWithPoints( Point midpoint, Point ab[] ) {
    Rectangle boundsOfAToB = new Rectangle();
    hitCircle.setCoefficients( 4, 4, midpoint.x, midpoint.y );
    double[] intersectionPoints = new double[4];
    Point pointA = (Point) points.get( 0 );
    int max = points.size();
    for ( int cntr = 1; cntr < max; cntr++ ) {
      final Point pointB = (Point) points.get( cntr );
      self.math.geom.RectangleUtils.updateRectangleToTlbr( boundsOfAToB, pointA.x, pointA.y, pointB.x, pointB.y );
      int v = 1, h = 1;
      if ( boundsOfAToB.width == 0 )
        h = 4;
      if ( boundsOfAToB.height == 0 )
        v = 4;
      boundsOfAToB.grow( h, v );
      if ( !boundsOfAToB.contains( midpoint.x, midpoint.y) ) {
        pointA = pointB;
        continue;
      }
      hitLine.setBasedOnPoints( pointA.x, pointA.y, pointB.x, pointB.y );
      if ( hitCircle.getIntersectingPoints(hitLine, intersectionPoints) ) {
        if ( ab != null ) {
          ab[0] = pointA;
          ab[1] = pointB;
        }
        return true;
      }
      pointA = pointB;
    }
    return false;
  }

  private void eatHandlePointIfJoinAttempted( int x, int y, Point handle ) {
    int max = points.size();
    if ( max == getMinEatHandles() )
      return;
    Rectangle dropZone = new Rectangle();
    int pos = getPositionOfPoint( handle );
    Point otherHandle = null;
    if ( pos == 0 ) {
      otherHandle = (Point) points.get( pos + 1 );
      arePointsCloseEnoughToJoin( dropZone, handle, otherHandle );
    } else if ( pos == max-1 ) {
      otherHandle = (Point) points.get( pos - 1 );
      arePointsCloseEnoughToJoin( dropZone, handle, otherHandle );
    } else {
      Point alpha = (Point) points.get( pos - 1 );
      if ( !arePointsCloseEnoughToJoin( dropZone, handle, alpha) ) {
        Point beta = (Point) points.get( pos + 1 );
        arePointsCloseEnoughToJoin( dropZone, handle, beta );
      }
    }
  }

  private boolean arePointsCloseEnoughToJoin( Rectangle dropZone, Point p1, Point p2 ) {
    self.math.geom.RectangleUtils.updateRectangleToTlbr( dropZone, p1.x, p1.y, p2.x, p2.y );
    boolean join = ( dropZone.width <= 6 && dropZone.height <= 6 );
    if ( join ) {
      int pos = getPositionOfPoint( p1 );
      points.remove( pos );
    }
    return join;
  }

  protected class PaintContext implements ILineRenderInfo {
    public boolean isDashed() {
      return isDashed;
    }

    public float getWidth() {
      return lineWidth;
    }

    public java.util.List getPoints() {
      return points;
    }

    public String getLineName() {
      return StringUtils.toNullOrNonEmptyValue( name );
    }

    public String getLineHeader() {
      return decorateHeader( StringUtils.toNullOrNonEmptyValue(header) );
    }

    public int getSideAHead() {
      return a.head;
    }

    public String getSideAMajor() {
      return StringUtils.toNullOrNonEmptyValue( a.major );
    }

    public String getSideAMinor() {
      return StringUtils.toNullOrNonEmptyValue( a.minor );
    }

    public int getSideBHead() {
      return b.head;
    }

    public String getSideBMajor() {
      return StringUtils.toNullOrNonEmptyValue( b.major );
    }

    public String getSideBMinor() {
      return StringUtils.toNullOrNonEmptyValue( b.minor );
    }

    public Color getColor() {
      if ( outlineOnly )
        return frameColor;
      return lineColorData.color;
    }

    public int getHeadDiameter() {
      return headDiameter;
    }
  }

  static public ComboBoxModel createHeadModel() {
    return new DefaultComboBoxModel( ILineRenderInfo.headStrings );
  }

  private boolean fetchIntersection( int xFrom, int yFrom, int[] xyPoint ) {
    boolean ret;
    Rectangle bounds = getBounds();
    Point centre = new Point( bounds.x + (bounds.width/2), bounds.y + (bounds.height/2) );
    LinearEquation mainLine = new LinearEquation( xFrom, yFrom, centre.x, centre.y );
    double[] xyTargets = new double[4];
    ret = self.math.geom.RectangleUtils.getRectangleLineIntersections( bounds, mainLine, xyTargets );
    if ( !ret )
      return ret;
    self.math.geom.RectangleUtils.updateRectangleToTlbr( bounds, (int) xyTargets[0], (int) xyTargets[1], (int) xyTargets[2], (int) xyTargets[3] );

    double[] xyCurrBestIntersection = new double[2], xyCurrStore = new double[2];
    double currBestDistance = Double.MAX_VALUE;
    LinearEquation currPathLineEq = new LinearEquation();
    Rectangle currPathBounds = new Rectangle();
    Point pointA = (Point) points.get( 0 );
    int max = points.size();
    int cntr = 1;
    boolean polygonTurn = false;
    while ( true ) {
      final Point pointB = (Point) points.get( cntr++ );
      self.math.geom.RectangleUtils.updateRectangleToTlbr( currPathBounds, pointA.x, pointA.y, pointB.x, pointB.y );
      if ( self.math.geom.RectangleUtils.getRectangleLineIntersections(currPathBounds, mainLine, xyTargets) ) {
        currPathLineEq.setBasedOnPoints( pointA.x, pointA.y, pointB.x, pointB.y );
        if ( currPathLineEq.getIntersectingPoints( mainLine, xyCurrStore) ) {
          currPathBounds.grow( 1, 1 );
          if ( currPathBounds.contains(xyCurrStore[0], xyCurrStore[1]) ) {
            double dist = self.math.geom.PointUtils.getDistance( xFrom, yFrom, xyCurrStore[0], xyCurrStore[1] );
            if ( dist < currBestDistance ) {
              xyCurrBestIntersection[0] = xyCurrStore[0];
              xyCurrBestIntersection[1] = xyCurrStore[1];
              currBestDistance = dist;
            }
          }
        }
      }
      pointA = pointB;
      if ( cntr == max ) {
        if ( !isPolygon )
          break;
        if ( !polygonTurn ) {
        	pointA = (Point) points.get( 0 );
          cntr--;
          polygonTurn = true;
        } else
          break;
      }
    }
    ret = currBestDistance < Double.MAX_VALUE;
    if ( ret ) {
      xyPoint[0] = (int) xyCurrBestIntersection[0];
      xyPoint[1] = (int) xyCurrBestIntersection[1];
    }
    return ret;
  }

//  static public void main( String[] args ) {
//    PolyLineView test = new PolyLineView();
//    test.addPoint( new Point(274, 238) );
//    test.addPoint( new Point(248, 225) );
//    test.addPoint( new Point(252, 263) );
//    test.addPoint( new Point(336, 263) );
//    test.addPoint( new Point(319, 218) );
//    test.addPoint( new Point(317, 229) );
//    // test.isPolygon = true;
//    test.resetPolygonFrame();
//    int[] xyStore = new int[2];
//    boolean val = test.fetchIntersection( 311, 149, xyStore );
//    System.out.println( "val=" + val + ", xyStore[0]=" + xyStore[0] + ", xyStore[1]=" + xyStore[1] );
//  }
}